package thread;

/**
 * @User: vitobo
 * @Date: 2024-07-29
 * @Description: 线程安全问题
 *  由于多线程的抢占式执行, 导致当前执行到任意一个指令的时候, 线程都可能被调度走, CPU让别的线程来执行
 *  代码执行顺序的可能性就会有许多种情况! 就需要保证这种无数种线程调度顺序的情况下, 代码执行的结果都正确.
 *
 *  2.线程安全问题的原因:
 *      根本原因: 抢占式执行, 随机调度
 *      代码结构: 多个线程同时修改同一个变量
 *      原则性: 如果是非原子的, 出现问题的概率就会很高  (原子: 不可拆分的基本单位)
 *      内存可见性问题: 如果是一个线程读, 一个线程改, 就可能会出现问题;
 *      指令重排排序, 调整代码的执行顺序 (本质上是编译器优化出bug了)
 *
 *
 *  线程安全问题解决办法: 加锁,通过加锁, 把不是原子的, 改为'原子' 的
 *      1.使用synchronized关键字修饰方法, 表示加锁, 确保在同一时间只有一个线程能够执行这个方法, 确保了线程安全;
 *          进入方法就会加锁, 出了方法就会解锁; 如果两个线程同时尝试加锁, 此时一个能获取锁成功, 另一个只能阻塞等待(BLOCKED),一直阻塞到刚才的线程释放锁(解锁),当前线程才能加锁成功;
 *
 *
 *  加锁: 是要明确执行对哪个对象加锁的,如果两个线程针对同一个对象加锁,会产生阻塞等待(锁竞争/锁冲突)
 *       如果两个线程针对不同对象加锁, 不会阻塞等待;
 *          锁对象相同, 就会产生锁竞争(产生阻塞等待), 锁对象不同就不会产生锁竞争(不会阻塞 等待)
 *
 *
 *  synchronized的使用方法:
 *      1.修饰方法: 进入方法就加锁, 离开方法就解锁
 *          修饰普通方法: 锁对象就是this
 *          修饰静态方法: 锁对象就是类对象
 *              这两种操作, 加锁的"对象"不同,
 *
 *      2.修饰代码块: 显式/手动指定锁对象
 *
 */

class Counter{
    public int count = 0;

    public void add(){
        // 修饰代码块, 进入代码块就加锁, 出了代码块就解锁
        synchronized (this){
            count++;
        }
    }

    public void add2(){
        count++;
    }

//    synchronized public void add(){
//        // 1.先把内存中的值, 读取到CPU的寄存器中, load
//        // 2.把CPU寄存器里的数值进行+1运算 add
//        // 3.把得到的结果写回到内存中;  save
//        count++;
//    }
}

public class ThreadDemo13 {
    // 解决线程不安全问题
    public static void main(String[] args) {
        final Counter counter = new Counter();

        Thread t1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("count= " + counter.count);
    }


    // 线程调度 充满随机性
    public static void main1(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        // 两个线程分别针对 counter 来调用 5万次 的 add方法
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println("count= " + counter.count);
    }


}
